
uniform sampler2DArray shadowCubeTexture;
uniform sampler2DArray shadowCascadeTexture;

#define LAMPS_LIB

layout(std140) uniform shadow_block {
	ShadowData        shadows_data[MAX_SHADOW];
	ShadowCubeData    shadows_cube_data[MAX_SHADOW_CUBE];
	ShadowCascadeData shadows_cascade_data[MAX_SHADOW_CASCADE];
};

layout(std140) uniform light_block {
	LightData lights_data[MAX_LIGHT];
};

/* type */
#define POINT          0.0
#define SUN            1.0
#define SPOT           2.0
#define AREA_RECT      4.0
/* Used to define the area lamp shape, doesn't directly correspond to a Blender lamp type. */
#define AREA_ELLIPSE 100.0

#if defined(SHADOW_VSM)
#define ShadowSample vec2
#define sample_cube(vec, id)    texture_octahedron(shadowCubeTexture, vec4(vec, id)).rg
#define sample_cascade(vec, id) texture(shadowCascadeTexture, vec3(vec, id)).rg
#elif defined(SHADOW_ESM)
#define ShadowSample float
#define sample_cube(vec, id)    texture_octahedron(shadowCubeTexture, vec4(vec, id)).r
#define sample_cascade(vec, id) texture(shadowCascadeTexture, vec3(vec, id)).r
#else
#define ShadowSample float
#define sample_cube(vec, id)    texture_octahedron(shadowCubeTexture, vec4(vec, id)).r
#define sample_cascade(vec, id) texture(shadowCascadeTexture, vec3(vec, id)).r
#endif

#if defined(SHADOW_VSM)
#define get_depth_delta(dist, s) (dist - s.x)
#else
#define get_depth_delta(dist, s) (dist - s)
#endif

/* ----------------------------------------------------------- */
/* ----------------------- Shadow tests ---------------------- */
/* ----------------------------------------------------------- */

#if defined(SHADOW_VSM)

float shadow_test(ShadowSample moments, float dist, ShadowData sd)
{
	float p = 0.0;

	if (dist <= moments.x)
		p = 1.0;

	float variance = moments.y - (moments.x * moments.x);
	variance = max(variance, sd.sh_bias / 10.0);

	float d = moments.x - dist;
	float p_max = variance / (variance + d * d);

	/* Now reduce light-bleeding by removing the [0, x] tail and linearly rescaling (x, 1] */
	p_max = clamp((p_max - sd.sh_bleed) / (1.0 - sd.sh_bleed), 0.0, 1.0);

	return max(p, p_max);
}

#elif defined(SHADOW_ESM)

float shadow_test(ShadowSample z, float dist, ShadowData sd)
{
	return saturate(exp(sd.sh_exp * (z - dist + sd.sh_bias)));
}

#else

float shadow_test(ShadowSample z, float dist, ShadowData sd)
{
	return step(0, z - dist + sd.sh_bias);
}

#endif

/* ----------------------------------------------------------- */
/* ----------------------- Shadow types ---------------------- */
/* ----------------------------------------------------------- */

float shadow_cubemap(ShadowData sd, ShadowCubeData scd, float texid, vec3 W)
{
	vec3 cubevec = W - scd.position.xyz;
	float dist = length(cubevec);

	cubevec /= dist;

	ShadowSample s = sample_cube(cubevec, texid);
	return shadow_test(s, dist, sd);
}

float evaluate_cascade(ShadowData sd, mat4 shadowmat, vec3 W, float range, float texid)
{
	vec4 shpos = shadowmat * vec4(W, 1.0);
	float dist = shpos.z * range;

	ShadowSample s = sample_cascade(shpos.xy, texid);
	float vis = shadow_test(s, dist, sd);

	/* If fragment is out of shadowmap range, do not occlude */
	if (shpos.z < 1.0 && shpos.z > 0.0) {
		return vis;
	}
	else {
		return 1.0;
	}
}

float shadow_cascade(ShadowData sd, int scd_id, float texid, vec3 W)
{
	vec4 view_z = vec4(dot(W - cameraPos, cameraForward));
	vec4 weights = smoothstep(
	        shadows_cascade_data[scd_id].split_end_distances,
	        shadows_cascade_data[scd_id].split_start_distances.yzwx,
	        view_z);

	weights.yzw -= weights.xyz;

	vec4 vis = vec4(1.0);
	float range = abs(sd.sh_far - sd.sh_near); /* Same factor as in get_cascade_world_distance(). */

	/* Branching using (weights > 0.0) is reaally slooow on intel so avoid it for now. */
	/* TODO OPTI: Only do 2 samples and blend. */
	vis.x = evaluate_cascade(sd, shadows_cascade_data[scd_id].shadowmat[0], W, range, texid + 0);
	vis.y = evaluate_cascade(sd, shadows_cascade_data[scd_id].shadowmat[1], W, range, texid + 1);
	vis.z = evaluate_cascade(sd, shadows_cascade_data[scd_id].shadowmat[2], W, range, texid + 2);
	vis.w = evaluate_cascade(sd, shadows_cascade_data[scd_id].shadowmat[3], W, range, texid + 3);

	float weight_sum = dot(vec4(1.0), weights);
	if (weight_sum > 0.9999) {
		float vis_sum = dot(vec4(1.0), vis * weights);
		return vis_sum / weight_sum;
	}
	else {
		float vis_sum = dot(vec4(1.0), vis * step(0.001, weights));
		return mix(1.0, vis_sum, weight_sum);
	}
}

/* ----------------------------------------------------------- */
/* --------------------- Light Functions --------------------- */
/* ----------------------------------------------------------- */

/* From Frostbite PBR Course
 * Distance based attenuation
 * http://www.frostbite.com/wp-content/uploads/2014/11/course_notes_moving_frostbite_to_pbr.pdf */
float distance_attenuation(float dist_sqr, float inv_sqr_influence)
{
	float factor = dist_sqr * inv_sqr_influence;
	float fac = saturate(1.0 - factor * factor);
	return fac * fac;
}

float spot_attenuation(LightData ld, vec3 l_vector)
{
	float z = dot(ld.l_forward, l_vector.xyz);
	vec3 lL = l_vector.xyz / z;
	float x = dot(ld.l_right, lL) / ld.l_sizex;
	float y = dot(ld.l_up, lL) / ld.l_sizey;
	float ellipse = inversesqrt(1.0 + x * x + y * y);
	float spotmask = smoothstep(0.0, 1.0, (ellipse - ld.l_spot_size) / ld.l_spot_blend);
	return spotmask;
}

float light_visibility(LightData ld, vec3 W,
#ifndef VOLUMETRICS
                       vec3 viewPosition,
                       vec3 viewNormal,
#endif
                       vec4 l_vector)
{
	float vis = 1.0;

	if (ld.l_type == SPOT) {
		vis *= spot_attenuation(ld, l_vector.xyz);
	}
	if (ld.l_type >= SPOT) {
		vis *= step(0.0, -dot(l_vector.xyz, ld.l_forward));
	}
	if (ld.l_type != SUN) {
		vis *= distance_attenuation(l_vector.w * l_vector.w, ld.l_influence);
	}

#if !defined(VOLUMETRICS) || defined(VOLUME_SHADOW)
	/* shadowing */
	if (ld.l_shadowid >= 0.0 && vis > 0.001) {
		ShadowData data = shadows_data[int(ld.l_shadowid)];

		if (ld.l_type == SUN) {
			vis *= shadow_cascade(
				data, int(data.sh_data_start),
				data.sh_tex_start, W);
		}
		else {
			vis *= shadow_cubemap(
				data, shadows_cube_data[int(data.sh_data_start)],
				data.sh_tex_start, W);
		}

#ifndef VOLUMETRICS
		/* Only compute if not already in shadow. */
		if (data.sh_contact_dist > 0.0) {
			vec4 L = (ld.l_type != SUN) ? l_vector : vec4(-ld.l_forward, 1.0);
			float trace_distance = (ld.l_type != SUN) ? min(data.sh_contact_dist, l_vector.w) : data.sh_contact_dist;

			vec3 T, B;
			make_orthonormal_basis(L.xyz / L.w, T, B);

			vec4 rand = texelfetch_noise_tex(gl_FragCoord.xy);
			rand.zw *= fast_sqrt(rand.y) * data.sh_contact_spread;

			/* We use the full l_vector.xyz so that the spread is minimize
			 * if the shading point is further away from the light source */
			vec3 ray_dir = L.xyz + T * rand.z + B * rand.w;
			ray_dir = transform_direction(ViewMatrix, ray_dir);
			ray_dir = normalize(ray_dir);

			vec3 ray_ori = viewPosition;

			if (dot(viewNormal, ray_dir) <= 0.0) {
				return vis;
			}

			float bias = 0.5; /* Constant Bias */
			bias += 1.0 - abs(dot(viewNormal, ray_dir)); /* Angle dependent bias */
			bias *= gl_FrontFacing ? data.sh_contact_offset : -data.sh_contact_offset;

			vec3 nor_bias = viewNormal * bias;
			ray_ori += nor_bias;

			ray_dir *= trace_distance;
			ray_dir -= nor_bias;

			vec3 hit_pos = raycast(-1, ray_ori, ray_dir, data.sh_contact_thickness, rand.x,
			                       0.1, 0.001, false);

			if (hit_pos.z > 0.0) {
				hit_pos = get_view_space_from_depth(hit_pos.xy, hit_pos.z);
				float hit_dist = distance(viewPosition, hit_pos);
				float dist_ratio = hit_dist / trace_distance;
				return vis * saturate(dist_ratio * dist_ratio * dist_ratio);
			}
		}
#endif
	}
#endif

	return vis;
}

#ifdef USE_LTC
float light_diffuse(LightData ld, vec3 N, vec3 V, vec4 l_vector)
{
	if (ld.l_type == AREA_RECT) {
		vec3 corners[4];
		corners[0] = normalize((l_vector.xyz + ld.l_right * -ld.l_sizex) + ld.l_up *  ld.l_sizey);
		corners[1] = normalize((l_vector.xyz + ld.l_right * -ld.l_sizex) + ld.l_up * -ld.l_sizey);
		corners[2] = normalize((l_vector.xyz + ld.l_right *  ld.l_sizex) + ld.l_up * -ld.l_sizey);
		corners[3] = normalize((l_vector.xyz + ld.l_right *  ld.l_sizex) + ld.l_up *  ld.l_sizey);

		return ltc_evaluate_quad(corners, N);
	}
	else if (ld.l_type == AREA_ELLIPSE) {
		vec3 points[3];
		points[0] = (l_vector.xyz + ld.l_right * -ld.l_sizex) + ld.l_up * -ld.l_sizey;
		points[1] = (l_vector.xyz + ld.l_right *  ld.l_sizex) + ld.l_up * -ld.l_sizey;
		points[2] = (l_vector.xyz + ld.l_right *  ld.l_sizex) + ld.l_up *  ld.l_sizey;

		return ltc_evaluate_disk(N, V, mat3(1.0), points);
	}
	else {
		float radius = ld.l_radius;
		radius /= (ld.l_type == SUN) ? 1.0 : l_vector.w;
		vec3 L = (ld.l_type == SUN) ? -ld.l_forward : (l_vector.xyz / l_vector.w);

		return ltc_evaluate_disk_simple(radius, dot(N, L));
	}
}

float light_specular(LightData ld, vec4 ltc_mat, vec3 N, vec3 V, vec4 l_vector)
{
	if (ld.l_type == AREA_RECT) {
		vec3 corners[4];
		corners[0] = (l_vector.xyz + ld.l_right * -ld.l_sizex) + ld.l_up *  ld.l_sizey;
		corners[1] = (l_vector.xyz + ld.l_right * -ld.l_sizex) + ld.l_up * -ld.l_sizey;
		corners[2] = (l_vector.xyz + ld.l_right *  ld.l_sizex) + ld.l_up * -ld.l_sizey;
		corners[3] = (l_vector.xyz + ld.l_right *  ld.l_sizex) + ld.l_up *  ld.l_sizey;

		ltc_transform_quad(N, V, ltc_matrix(ltc_mat), corners);

		return ltc_evaluate_quad(corners, vec3(0.0, 0.0, 1.0));
	}
	else {
		bool is_ellipse = (ld.l_type == AREA_ELLIPSE);
		float radius_x = is_ellipse ? ld.l_sizex : ld.l_radius;
		float radius_y = is_ellipse ? ld.l_sizey : ld.l_radius;

		vec3 L = (ld.l_type == SUN) ? -ld.l_forward : l_vector.xyz;
		vec3 Px = ld.l_right;
		vec3 Py = ld.l_up;

		if (ld.l_type == SPOT || ld.l_type == POINT) {
			make_orthonormal_basis(l_vector.xyz / l_vector.w, Px, Py);
		}

		vec3 points[3];
		points[0] = (L + Px * -radius_x) + Py * -radius_y;
		points[1] = (L + Px *  radius_x) + Py * -radius_y;
		points[2] = (L + Px *  radius_x) + Py *  radius_y;

		return ltc_evaluate_disk(N, V, ltc_matrix(ltc_mat), points);
	}
}
#endif

#define MAX_SSS_SAMPLES 65
#define SSS_LUT_SIZE 64.0
#define SSS_LUT_SCALE ((SSS_LUT_SIZE - 1.0) / float(SSS_LUT_SIZE))
#define SSS_LUT_BIAS (0.5 / float(SSS_LUT_SIZE))

#ifdef USE_TRANSLUCENCY
layout(std140) uniform sssProfile {
	vec4 kernel[MAX_SSS_SAMPLES];
	vec4 radii_max_radius;
	int sss_samples;
};

uniform sampler1D sssTexProfile;

vec3 sss_profile(float s) {
	s /= radii_max_radius.w;
	return texture(sssTexProfile, saturate(s) * SSS_LUT_SCALE + SSS_LUT_BIAS).rgb;
}
#endif

vec3 light_translucent(LightData ld, vec3 W, vec3 N, vec4 l_vector, float scale)
{
#if !defined(USE_TRANSLUCENCY) || defined(VOLUMETRICS)
	return vec3(0.0);
#else
	vec3 vis = vec3(1.0);

	if (ld.l_type == SPOT) {
		vis *= spot_attenuation(ld, l_vector.xyz);
	}
	if (ld.l_type >= SPOT) {
		vis *= step(0.0, -dot(l_vector.xyz, ld.l_forward));
	}
	if (ld.l_type != SUN) {
		vis *= distance_attenuation(l_vector.w * l_vector.w, ld.l_influence);
	}

	/* Only shadowed light can produce translucency */
	if (ld.l_shadowid >= 0.0 && vis.x > 0.001) {
		ShadowData data = shadows_data[int(ld.l_shadowid)];
		float delta;

		vec4 L = (ld.l_type != SUN) ? l_vector : vec4(-ld.l_forward, 1.0);

		vec3 T, B;
		make_orthonormal_basis(L.xyz / L.w, T, B);

		vec4 rand = texelfetch_noise_tex(gl_FragCoord.xy);
		rand.zw *= fast_sqrt(rand.y) * data.sh_blur;

		/* We use the full l_vector.xyz so that the spread is minimize
		 * if the shading point is further away from the light source */
		W = W + T * rand.z + B * rand.w;

		if (ld.l_type == SUN) {
			int scd_id = int(data.sh_data_start);
			vec4 view_z = vec4(dot(W - cameraPos, cameraForward));

			vec4 weights = step(shadows_cascade_data[scd_id].split_end_distances, view_z);
			float id = abs(4.0 - dot(weights, weights));

			if (id > 3.0) {
				return vec3(0.0);
			}

			float range = abs(data.sh_far - data.sh_near); /* Same factor as in get_cascade_world_distance(). */

			vec4 shpos = shadows_cascade_data[scd_id].shadowmat[int(id)] * vec4(W, 1.0);
			float dist = shpos.z * range;

			if (shpos.z > 1.0 || shpos.z < 0.0) {
				return vec3(0.0);
			}

			ShadowSample s = sample_cascade(shpos.xy, data.sh_tex_start + id);
			delta = get_depth_delta(dist, s);
		}
		else {
			vec3 cubevec = W - shadows_cube_data[int(data.sh_data_start)].position.xyz;
			float dist = length(cubevec);
			cubevec /= dist;

			ShadowSample s = sample_cube(cubevec, data.sh_tex_start);
			delta = get_depth_delta(dist, s);
		}

		/* XXX : Removing Area Power. */
		/* TODO : put this out of the shader. */
		float falloff;
		if (ld.l_type == AREA_RECT || ld.l_type == AREA_ELLIPSE) {
			vis *= (ld.l_sizex * ld.l_sizey * 4.0 * M_PI) * (1.0 / 80.0);
			if (ld.l_type == AREA_ELLIPSE) {
				vis *= M_PI * 0.25;
			}
			vis *= 0.3 * 20.0 * max(0.0, dot(-ld.l_forward, l_vector.xyz / l_vector.w)); /* XXX ad hoc, empirical */
			vis /= (l_vector.w * l_vector.w);
			falloff = dot(N, l_vector.xyz / l_vector.w);
		}
		else if (ld.l_type == SUN) {
			vis /= 1.0f + (ld.l_radius * ld.l_radius * 0.5f);
			vis *= ld.l_radius * ld.l_radius * M_PI; /* Removing area light power*/
			vis *= M_2PI * 0.78; /* Matching cycles with point light. */
			vis *= 0.082; /* XXX ad hoc, empirical */
			falloff = dot(N, -ld.l_forward);
		}
		else {
			vis *= (4.0 * ld.l_radius * ld.l_radius) * (1.0 /10.0);
			vis *= 1.5; /* XXX ad hoc, empirical */
			vis /= (l_vector.w * l_vector.w);
			falloff = dot(N, l_vector.xyz / l_vector.w);
		}
		// vis *= M_1_PI; /* Normalize */

		/* Applying profile */
		vis *= sss_profile(abs(delta) / scale);

		/* No transmittance at grazing angle (hide artifacts) */
		vis *= saturate(falloff * 2.0);
	}
	else {
		vis = vec3(0.0);
	}

	return vis;
#endif
}
